package client // Package client import "xemo.top/docker/docker/client"
import (
	"context"
	"encoding/base64"
	"encoding/json"
	"fmt"
	"io"
	"net/http"
	"net/url"
	"path/filepath"
	"strings"
	"xemo.top/docker/docker/api/types"
)

// ContainerStatPath 返回有关容器文件系统内路径的Stat信息
// ContainerStatPath returns Stat information about a path inside the container filesystem
func (cli *Client) ContainerStatPath(ctx context.Context, containerID, path string) (types.ContainerPathStat, error) {
	query := url.Values{}
	// 规范化API中使用的路径
	// Normalize the paths used in the API
	query.Set("path", filepath.ToSlash(path))
	urlStr := "/containers/" + containerID + "/archive"
	response, err := cli.head(ctx, urlStr, query, nil)
	defer ensureReaderClosed(response)
	if err != nil {
		return types.ContainerPathStat{}, wrapResponseError(err, response, "container:path", containerID+":"+path)
	}
	return getContainerPathStatFromHeader(response.header)
}

func getContainerPathStatFromHeader(header http.Header) (types.ContainerPathStat, error) {
	var stat types.ContainerPathStat
	encodeStat := header.Get("X-Docker-Container-Path-Stat")
	statDecoder := base64.NewDecoder(base64.StdEncoding, strings.NewReader(encodeStat))
	err := json.NewDecoder(statDecoder).Decode(&stat)
	if err != nil {
		err = fmt.Errorf("unable to decode container path stat header:%s", err)
	}
	return stat, err
}

// CopyFromContainer 从容器中获取内容，并将其作为TAR存档的读取器返回，以在主机中对其进行操作。
// CopyFromContainer gets the content from the container and returns it as a Reader for a TAR archive to manipulate it in the host.It's up to the caller to close the reader.
func (cli *Client) CopyFromContainer(ctx context.Context, containerID, srcPath string) (io.ReadCloser, types.ContainerPathStat, error) {
	query := make(url.Values, 1)
	// 规范化API中使用的路径
	// Normalize the paths used in the API
	query.Set("path", filepath.ToSlash(srcPath))
	apiPath := "/containers/" + containerID + "/archive"
	response, err := cli.get(ctx, apiPath, query, nil)
	if err != nil {
		return nil, types.ContainerPathStat{}, wrapResponseError(err, response, "container:path", containerID+":"+srcPath)
	}
	// TODO this code converts non-server status-codes(e.g.,"204 No Content")into an error;verify if this is the desired behavior
	if response.statusCode != http.StatusOK {
		return nil, types.ContainerPathStat{}, fmt.Errorf("unexpected status code from daemon:%d", response.statusCode)
	}
	// 为了获得正确的复制行为，我们需要知道关于源和目标的信息。响应头包括关于源的统计信息，我们可以使用它来决定如何在本地复制它。除了关于本地目标的统计信息，我们拥有一切，我们需要处理多种可能性，可以将文件/目录从一个位置复制到另一个文件/目录时
	// In order to get the copy behavior right,we need to know information about both the source and the destination.The response headers include stat info about the source that we can use in deciding exactly how to copy it locally.Along with the stat info about the local destination,we have everything we need to handle the multiple possibilities there can be when copying a file/dir from one location to another file/dir
	stat, err := getContainerPathStatFromHeader(response.header)
	if err != nil {
		return nil, stat, fmt.Errorf("unable to get resource stat from response:%s", err)
	}
	return response.body, stat, err
}

// CopyToContainer 将内容复制到容器文件系统中。并不是说 “内容” 必须是TAR存档的读者
// CopyToContainer copies content into the container filesystem. Not that `content` must be a Reader for a TAR archive
func (cli *Client) CopyToContainer(ctx context.Context, containerID, dstPath string, content io.Reader, options types.CopyToContainerOptions) error {
	query := url.Values{}
	// 规范化API中使用的路径。
	// Normalize the paths used in the API.
	// 不允许现有目录被非目录覆盖，反之亦然
	// Do not allow for an existing directory to be overwritten by a non-directory and vice versa
	query.Set("path", filepath.ToSlash(dstPath))
	if !options.AllowOverwriteDirWithFile {
		query.Set("noOverwriteDirNonDir", "true")
	}
	if options.CopyUIDGID {
		query.Set("copyUIDGID", "true")
	}
	apiPath := "/containers/" + containerID + "/archive"
	response, err := cli.putRaw(ctx, apiPath, query, content, nil)
	defer ensureReaderClosed(response)
	if err != nil {
		return wrapResponseError(err, response, "container:path", containerID+":"+dstPath)
	}
	// TODO this code converts non-error status-codes (e.g., "204 No Content")into an error;verify if this is the desired behavior
	if response.statusCode != http.StatusOK {
		return fmt.Errorf("unexpected status code from daemon:%d", response.statusCode)
	}
	return nil
}
